package org.fujene.memoryblock;

import java.io.IOException;
import java.util.Arrays;

import org.fujene.constants.Constants;
import org.fujene.diskblock.DiskBlock;

public abstract class MemoryBlock{
	private int ID;
	private int type;
	protected int headerSize;
	protected int bodySize;
	private byte[] body;
	protected int bodyVacancy;
	private boolean isAboutToFlush;

	protected static final int PTR = Constants.BYTE_GLOBALPTR;
	protected static final int RELPTR = Constants.BYTE_RELATIVEPTR;
	protected static final int STRLEN = Constants.BYTE_STRLEN;
	protected static final int DOCFREQ = Constants.BYTE_DOCFREQ;
	protected static final int TERMFREQ = Constants.BYTE_TERMFREQ;
	protected static final int IDDIFF = Constants.BYTE_INVINDEXDIFF;

	public MemoryBlock(int bodySize, int ID, int type, boolean isAllocate){
		this.ID = ID;
		this.bodySize = bodySize;
		this.type = type;
		if(isAllocate){
			this.body = new byte[bodySize];
			bodyVacancy = bodySize;
		}
		this.isAboutToFlush = false;
	}

	public MemoryBlock(byte[] body, int bodySize, int ID, int type, boolean isAllocate){
		this.ID = ID;
		this.bodySize = bodySize;
		this.type = type;
		if(isAllocate){
			this.body = body;
			this.bodySize = body.length;
			this.bodyVacancy = this.bodySize;
		}
		this.isAboutToFlush = false;
	}

	public MemoryBlock(int bodySize, int ID, boolean isAllocate){
		this(bodySize, ID, Constants.MEMORY_TYPE_DICT, isAllocate);
	}

	public MemoryBlock(int bodySize, int ID){
		this(bodySize, ID, true);
	}

	public int getID(){
		return ID;
	}

	public void resetID(int newID){
		this.ID = newID;
	}

	public int getBodySize(){
		return this.bodySize;
	}

	public abstract int getOption(int num);

	public abstract int getOptionSize();

	public abstract byte[] getOptionByteStream();

	public abstract void setOption(int option, int value);

	public abstract byte[] getData() throws IOException;

	public abstract void setData(byte[] array) throws IOException;

	// read with copy
	public byte[] getBody(int from, int length){
		if(from + length > bodySize || length < 0)
			throw new IndexOutOfBoundsException("Out of bound from " + from + ", length: " + length);

		return Arrays.copyOfRange(body, from, from + length);
	}

	protected void moveBody(int from, int length, int offset){
		if(offset == 0) return;
		synchronized(body){
			if(offset > 0){
				// move backward from the last byte
				for(int i = from + length - 1; i >= from; i--)
					body[i + offset] = body[i];
			}else{
				// move forward from the first byte
				for(int i = from; i < from + length - 1; i++)
					body[i + offset] = body[i];
			}
		}
	}

	public int getType(){
		return type;
	}

	public void setBody(byte[] values, int from){
		if(from + values.length > bodySize) return;

		synchronized(body){
			for(int i = 0; i < values.length; i++)
				body[from + i] = values[i];
		}
	}

	public void setBody(byte[] values, int vfrom, int vlen, int bfrom){
		if(bfrom + vlen > bodySize) return;

		synchronized(body){
			for(int i = 0; i < vlen; i++)
				body[bfrom + i] = values[vfrom + i];
		}
	}

	public void setType(int type){
		this.type = type;
	}

	public int getUsed(){
		return this.bodySize - this.bodyVacancy;
	}

	public int getBodyVacancy(){
		return bodyVacancy;
	}

	public void setBodyVacancy(int bodyVacancy){
		this.bodyVacancy = bodyVacancy;
	}

	public void setAboutToFlush(boolean isAboutToFlush){
		this.isAboutToFlush = isAboutToFlush;
	}

	public boolean isAboutToFlush(){
		return isAboutToFlush;
	}

	public byte[] getBorrowedBody(){
		return body;
	}

	public void resetBlockVacancy(){
		this.bodyVacancy = this.bodySize;
	}

	public void storeRawData(int ptr, byte[] b){
		if(ptr + b.length >= this.bodySize)
			throw new IndexOutOfBoundsException("point index exceeds block size: " + ptr + "+" + b.length
					+ " > " + this.bodySize);
		this.bodyVacancy -= b.length;
		setBody(b, ptr);
	}
	
	public abstract String getClassName();
	public abstract DiskBlock toDisk(int from, int to, String dir);
}